#pragma once
// 英汉互译服务器（版本更替与实现方法有关，服务器种类跟service服务函数有关)

// udp 面向数据报，不可靠的
// tcp 面向字节流，可靠的
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <cassert>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include <memory>
#include <ctype.h>
#include <unordered_map>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include "Log.hpp"

// #include "ThreadPool/log.hpp"
// #include "ThreadPool/thread.hpp"
// #include "ThreadPool/Task.hpp"
// #include "ThreadPool/threadPool.hpp"

// /////////////////////////////////////
// // verson4.0 --线程池版本 （预先创建一批线程，当来了链接时候，再直接读取任务，投递到任务队列中。然后派发给线程池中的线程中，自动挑选线程来执行任务）

// // 同时在线10人。后续来的一大批客户端，他们的请求会放在任务队列中，等前面的10人聊完之后，才能让后面的客户端聊
// // 所有，我们一般服务器进程业务处理，如果是从连上，到断开，要一直保持这个链接--长链接。
// // 一般是很少出现这种不退出的长链接。
// // 后面有其他方案。

// // 大小写转化
// static void dicOnline(int sock, const std::string &clientip, const uint16_t &clientport, const std::string &thread_name)//服务员
// {
//     // signal(SIGCHLD, SIG_IGN); // verson2.0:对SIGCHLD，主动忽略SIGCHLD信号，子进程退出的时候，会自动释放自己的僵尸状态，不需要父进程等
//     // echo server
//     char buffer[1024];
//     static std::unordered_map<std::string,std::string> dict={{"apple","苹果"},{"bite","比特"},{"banana","香蕉"},{"hard","好难啊"}};//映射关系可以写到文件里，然后放这个static到类内，在类生成的时候就加载文件内容到这staitc dic里面。

//     // tcp面向字节流接口  udp面向数据报
//     // 在流式套接里（就和文件系统那里的接口一样，就从普通文件里读是一样的），
//     // read和write可以直接被使用

//     // 服务端读
//     ssize_t s = read(sock, buffer, sizeof(buffer) - 1); // 从sock中读，udp中用的是recvfrom
//     if (s > 0)
//     {
//         buffer[s] = 0; // 将发过来的数据当做字符串
//         std::cout << thread_name << " | " << clientip << ":" << clientport << "# " << buffer << std::endl;
//         std::string message;  // 用于输出的处理结果
//         auto iter =dict.find(buffer);
//         if(iter==dict.end())message="我不知道...";
//         else message=iter->second;
//         write(sock, message.c_str(), message.size()); // 服务端写回
//     }

//     else if (s == 0) // 读到\0 代表 对端关闭链接
//     {
//         // todo
//         logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
//     }
//     else
//     {
//         // todo
//         logMessage(ERROR, "read socket error,%d:%s", errno, strerror(errno));
//     }

//     close(sock); // 这里是sock，也就是serversock，这里是服务端服务员运行完了，才关闭serversock
//     // 这个必须关。因为占着又不用，不关掉，因为每次的servicesock不一样，所以整个进程中的描述符表会越来越少。上限是5w-10w。用ulimit命令可以查看
//     // 子进程使用完再关servicesock，也就是这里的sock
// }

// class TcpServer
// {
// private:
//     const static int gbacklog = 20; // 一般不能太大，也不能太小。目前先学会使用，以后会讲解。

// public:
//     TcpServer(uint16_t port, std::string ip = "")
//         : _listensock(-1),
//           _port(port),
//           _ip(ip),
//           _threadpool_ptr(ThreadPool<Task>::getThreadPool())
//     {
//     }

//     void initServer()
//     {
//         // 1.创建套接字  创建网络文件
//         _listensock = socket(AF_INET /*网络通信*/, SOCK_STREAM /*流式套接*/, 0);
//         if (_listensock < 0)
//         {
//             logMessage(FATAL, "create socket error,%d:%s", errno, strerror(errno));
//             exit(2);
//         }

//         logMessage(NORMAL, "create socket success,listensock: %d", _listensock); // 3 文件描述符

//         // 2.绑定套接字  将server端进程与 ip#port绑定，并绑定server端与sock;//一个端口号不能被多个端口（进程）绑定
//         struct sockaddr_in local;
//         memset(&local, 0, sizeof local);
//         local.sin_family = AF_INET;
//         local.sin_port = htons(_port);

//         //法1.
//         local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());//点分十进制字符串转四字节主机序列
//         //法2.搭配构造函数的ip缺省参数为"0.0.0.0"使用
//         //用法:inet_aton(_ip.c_str(),&local.sin_addr)//点分十进制字符串赋值到结构体里面，也是点分十进制转四字节主机序列的，只不过是赋值到结构体里面

//         //法3.inet_pton(AF_INET,_ip.c_str(),&local.sin_addr/*缓冲区，用于接收数据的初始化，一般就是传结构体进来让其初始化该结构体*/);
//         if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
//         {
//             logMessage(FATAL, "bind error,%d:%s", errno, strerror(errno));
//             exit(3);
//         }

//         // 3.因为TCP是面向连接的，当我们正式通信的时候，需要先建立链接
//         // 将套接字状态设置为监听状态listen
//         if (listen(_listensock, gbacklog) < 0) // gbacklog全链接队列的长度
//         {
//             logMessage(FATAL, "listen error,%d:%s", errno, strerror(errno));
//             exit(4);
//         }

//         logMessage(NORMAL, "init server success");
//     }

//     void Start()
//     {
//         while (true)
//         {
//             _threadpool_ptr->run(); // step1. 让线程池跑起来，线程池就会创建线程，线程的例程函数的内容就是不断判断任务队列是否为空，若不为空，则不断从任务队列中获取任务
//             // 然后不断向线程池里面push任务即可。任务数据来源从网络中来

//             // 4.获取链接(udp直接发直接收，而tcp先要获取链接才能发和收)--接客员
//             // int accpet(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
//             // addr是输出型参数。可以将数据从函数内部发送到函数外部。
//             // addrlen是输入输出型参数。和recvfrom含义一模一样。可以将数据从函数内部发送到函数外部，也可以把数据从函数外部输入到函数内部
//             // 返回值：成功返回接收的套接字，失败返回-1.
//             struct sockaddr_in src;//用于接收输出的参数
//             socklen_t len = sizeof(src);

//             // servicesock (fd李四，王五等提供服务的服务员) vs listensock (张三---获取新链接(拉客员))
//             int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);//和recvfrom类似，获取客户端发过来的数据。并且派生服务员。
//             if (servicesock < 0) // 拉客失败(获取链接失败)
//             {
//                 logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
//                 continue;
//             }

//             // 获取链接成功
//             uint16_t client_port = ntohs(src.sin_port);      // inet_addr();把字符串转为网络序列
//             std::string client_ip = inet_ntoa(src.sin_addr); // inet_ntoa;把网络序列转为字符串
//             logMessage(NORMAL, "link success,servicesock: %d | %s : %d |\n",
//                        servicesock, client_ip.c_str(), client_port);
//             // 开始通信服务  ///////////////////
//             // verson4.线程池版
//             // 用法:
//             // 1.创建、初始化任务
//             // 2.放入线程池
//             Task t(servicesock, client_ip, client_port, dicOnline); // step3. 初始化任务,任务里面也初始化了仿函数
//             _threadpool_ptr->pushTask(t);                        // step4. push进线程池
//         }
//     }
//     ~TcpServer()
//     {
//     }

// private:
//     uint16_t _port;
//     std::string _ip;
//     int _listensock;
//     std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
// };







/////////////////////////////////////
//封装sock
class Sock
{
private:
    const static int gbacklog ;

public:
    Sock() {}

    int Socket() // 一、创建套接字
    {
        // 1.创建套接字  创建网络文件
        int listensock = socket(AF_INET /*网络通信*/, SOCK_STREAM /*流式套接*/, 0);
        if (listensock < 0)
        {
            logMessage(FATAL, "create socket error,%d:%s", errno, strerror(errno));
            exit(2);
        }

        logMessage(NORMAL, "create socket success,listensock: %d", listensock); // 3 文件描述符

        return listensock;



        //// my
        // int listensock=socket(AF_INET,SOCK_STREAM,0);
        // if(listensock<0)
        // {
        //     logMessage(FATAL,"create socket error,%d:%s",errno,strerror(error));
        //     exit(2);
        // }
        // return listensock;
    }

    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0") // 二、绑定当前指定ip、port和sock，使指定ip可以从sock中拿数据。（绑定服务端ip,port与接客员套接字，以便接客员可以获取服务员套接字给服务端ip,port)
    {
        // 2.绑定套接字  将server端进程与 ip#port绑定，并绑定server端与sock;//一个端口号不能被多个端口（进程）绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = ip.empty() ? INADDR_ANY : inet_addr(ip.c_str()); // 点分十进制字符串转四字节主机序列

        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error,%d:%s", errno, strerror(errno));

            exit(3);
        }


        // //my
        // struct sockaddr_in local;
        // memset(&local,0,sizeof(local));
        // local.sin_family=AF_INET;
        // local.sin_port=htons(port);//主机转网络(整形->网络序列)
        // local.sin_addr.s_addr=ip.empty()?INADDR_ANY:inet_addr(ip.c_str());//主机转网络(字符串->网络序列)
        // if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
        // {
        //     logMessage(FATAL,"bind error,%d:%s",errno,strerror(errno));
        //     exit(3);
        // }
    }

    void Listen(int sock) // 三、将(当前进程)服务端设置为监听状态，监听
    {
        if (listen(sock, gbacklog) < 0) // gbacklog全链接队列的长度
        {
            logMessage(FATAL, "listen error,%d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(NORMAL, "init server success");


        // ////my
        // if(listen(sock,gbacklog)<0)
        // {
        //     logMessage(FATAL,"listen error%d:%s",errno,strerror(errno));
        //     exit(4);
        // }
        // logMessage(NORMAL,"listen success,%d:%s",errno,strerror(errno));
        // logMessage(NORMAL,"init server success,%d:%s",errno,strerror(errno));
    }

    int Accept(int sock, std::string *ip, uint16_t *port) // 输出型参数 四、接客员获取链接，提供给服务员
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);

        // servicesock (fd李四，王五等提供服务的服务员) vs listensock (张三---获取新链接(拉客员))
        int servicesock = accept(sock/*listensock*/, (struct sockaddr *)&src, &len); // 和recvfrom类似，获取客户端发过来的数据。并且派生服务员。
        if (servicesock < 0)                                           // 拉客失败(获取链接失败)
        {
            logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }
        if (port)
            *port = ntohs(src.sin_port); // 如果外部给了指针接收，则可以用输出型参数输出
        if (ip)
            *ip = inet_ntoa(src.sin_addr); // 如果外部给了指针接收，则可以用输出型参数输出
        return servicesock;//返回服务员套接字
        


        // //my
        // struct sockaddr_in src;
        // socklen_t len=sizeof(src);
        
        // int serversock =accept(sock,(struct sockaddr*)&src,&len);
        // if(serversock<0)
        // {
        //     logMessage(FATAL,"accpet error,%d:%s",errno,strerror(errno));
        //     return -1;
        // }
        // logMessage(NORMAL,"accpet success,serversock:%d",serversock);
        // if(port)*port=ntohs(src.sin_port);
        // if(ip)*ip=inet_ntoa(src.sin_addr);//直接提取其中的ip的接口(网络序列转->字符串)
        // return servicesock;//返回服务员套接字



// struct sockaddr_in src;//用于接收输出的参数
// socklen_t len = sizeof(src);

        // servicesock (fd李四，王五等提供服务的服务员) vs listensock (张三---获取新链接(拉客员))
//             int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);//和recvfrom类似，获取客户端发过来的数据。并且派生服务员。
//             if (servicesock < 0) // 拉客失败(获取链接失败)
//             {
//                 logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
//                 continue;
//             }

    }

    //五、客户端连接链接(本质上就是将客户端和serversock进行链接,让serversock负责处理客户端的数据)
    bool Connect(int sock, const std::string &server_ip , const uint16_t &server_port)/*输入型参数*/ //五.让服务员去连接（服务端)新链接(服务员套接字与服务端ip链接)--即服务端与服务员套接字关联到一起，以便后续服务端在服务员套接字中进行网络通信
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == 0)
            return true;
        else
            return false;

        
        // struct sockaddr_in server;
        // memset(&server,0,sizeof(server));
        // server.sin_family=AF_INET;
        // server.sin_port=htons(server_port);
        // server.sin_addr.s_addr=inet_addr(server_ip.c_str());//(字符串->网络序列)
        
        // if(connect(sock,(struct sockaddr*)&server,sizeof(server)))
        //     return true;
        // else
        //     return false;





                // 获取链接成功
//             uint16_t client_port = ntohs(src.sin_port);      // inet_addr();把字符串转为网络序列
//             std::string client_ip = inet_ntoa(src.sin_addr); // inet_ntoa;把网络序列转为字符串
//             logMessage(NORMAL, "link success,servicesock: %d | %s : %d |\n",
//                        servicesock, client_ip.c_str(), client_port);
//             // 开始通信服务  ///////////////////
//             // verson4.线程池版
//             // 用法:
//             // 1.创建、初始化任务
//             // 2.放入线程池
//             Task t(servicesock, client_ip, client_port, dicOnline); // step3. 初始化任务,任务里面也初始化了仿函数
//             _threadpool_ptr->pushTask(t);                        // step4. push进线程池
    }



    // const int&  -输入型参数
    // int* -输出型参数
    // int& -输入输出型参数

    ~Sock(){}
};


const int Sock::gbacklog=10;